
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Data;

using Chronicle.Analyzation;
using Chronicle.Parser;

namespace Chronicle.Modules
{
	public class DamageModule : AnalyzeModule
	{
		AnalyzeVisitor visitor;
		
		public override string Name {
			get {
				return "Damage";
			}
		}

		#region Event analyzation
		class TargetData
		{
			public string TargetName {
				get;
				set;
			}
			
			public long Amount {
				get;
				set;
			}
			
			public Dictionary<string, SpellData> Damage     = new Dictionary<string, SpellData> ();
			public Dictionary<string, SpellData> CritDamage = new Dictionary<string, SpellData> ();
			
			public SpellData GetSpellData(Event evt)
			{
				var spellDataCollection = evt.Critical ? CritDamage : Damage;
				SpellData data;
				if (!spellDataCollection.TryGetValue(evt.AbilityId, out data))
					spellDataCollection[evt.AbilityId] = data = new SpellData(evt.Ability, evt.AbilityId) { Min = evt.Amount, Max = evt.Amount };
				return data;
			}
		}
		
		class DamageTag
		{
			public long TotalDamage = 0;
			public Dictionary<string, SpellData> Damage     = new Dictionary<string, SpellData> ();
			public Dictionary<string, SpellData> CritDamage = new Dictionary<string, SpellData> ();
			
			public Dictionary<string, TargetData> TargetData     = new Dictionary<string, TargetData> ();
			
			public SpellData GetSpellData(Event evt)
			{
				var spellDataCollection = evt.Critical ? CritDamage : Damage;
				SpellData data;
				if (!spellDataCollection.TryGetValue(evt.AbilityId, out data))
					spellDataCollection[evt.AbilityId] = data = new SpellData(evt.Ability, evt.AbilityId) { Min = evt.Amount, Max = evt.Amount };
				return data;
			}
			
			public TargetData GetTargetData (Event evt)
			{
				TargetData data;
				if (!TargetData.TryGetValue(evt.TargetId, out data))
					TargetData[evt.TargetId] = data = new TargetData() { TargetName = evt.TargetName };
				return data;
			}
		}

		public override void Attach (AnalyzeVisitor visitor)
		{
			this.visitor = visitor;
			visitor.EventDamage += HandleEventDamage;
			visitor.EventAvoidAttack += HandleVisitorEventAvoidAttack;
			visitor.EventParryAttack += HandleVisitorEventParryAttack;

			visitor.EventMissedAttack += HandleVisitorEventMissedAttack;
			visitor.EventResistAttack += HandleVisitorEventResistAttack;
		}

		void HandleVisitorEventResistAttack (object sender, EventEventArgs e)
		{
			var curEvent = visitor.curEvent;
			var eventDamage = e.Event;
			var actor = curEvent.GetActor(eventDamage);
			var tagData = actor.GetTagData (() => new DamageTag());

			SpellData data = tagData.GetSpellData (eventDamage);
			data.Resisted++;
			var targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Resisted++;
			
			actor = visitor.allEvents.GetActor(eventDamage);
			tagData = actor.GetTagData (() => new DamageTag());
			data = tagData.GetSpellData (eventDamage);
			data.Resisted++;
			targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Resisted++;
		}

		void HandleVisitorEventParryAttack (object sender, EventEventArgs e)
		{
			var curEvent = visitor.curEvent;
			var eventDamage = e.Event;
			var actor = curEvent.GetActor(eventDamage);
			var tagData = actor.GetTagData (() => new DamageTag());

			SpellData data = tagData.GetSpellData (eventDamage);
			data.Parried++;
			var targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Parried++;
			

			actor = visitor.allEvents.GetActor(eventDamage);
			tagData = actor.GetTagData (() => new DamageTag());
			data = tagData.GetSpellData (eventDamage);
			data.Parried++;
			targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Parried++;
			
		}

		void HandleVisitorEventMissedAttack (object sender, EventEventArgs e)
		{
			var curEvent = visitor.curEvent;
			var eventDamage = e.Event;
			var actor = curEvent.GetActor(eventDamage);
			var tagData = actor.GetTagData (() => new DamageTag());

			SpellData data = tagData.GetSpellData (eventDamage);
			data.Missed++;
			var targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Missed++;
			

			actor = visitor.allEvents.GetActor(eventDamage);
			tagData = actor.GetTagData (() => new DamageTag());
			data = tagData.GetSpellData (eventDamage);
			data.Missed++;
			targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Missed++;
		}

		void HandleVisitorEventAvoidAttack (object sender, EventEventArgs e)
		{
			var curEvent = visitor.curEvent;
			var eventDamage = e.Event;
			var actor = curEvent.GetActor(eventDamage);
			var tagData = actor.GetTagData (() => new DamageTag());
			SpellData data = tagData.GetSpellData (eventDamage);
			data.Avoided++;
			
			var targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Avoided++;
			

			actor = visitor.allEvents.GetActor(eventDamage);
			tagData = actor.GetTagData (() => new DamageTag());
			data = tagData.GetSpellData (eventDamage);
			data.Avoided++;
			
			targetData = tagData.GetTargetData (eventDamage);
			data = targetData.GetSpellData (eventDamage);
			data.Avoided++;
			
		}

		void HandleEventDamage (object sender, EventEventArgs e)
		{
			var curEvent = visitor.curEvent;
			var eventDamage = e.Event;
			var actor = curEvent.GetActor(eventDamage);
			var tagData = actor.GetTagData (() => new DamageTag());
			tagData.TotalDamage += eventDamage.Amount;
			SpellData data = tagData.GetSpellData (eventDamage);

			data.TotalAmount += eventDamage.Amount;
			data.Max = Math.Max (data.Max, eventDamage.Amount);
			data.Min = Math.Min (data.Min, eventDamage.Amount);
			data.Times++;
			actor.LastAction = eventDamage.TimeStamp;
			
			var targetData = tagData.GetTargetData (eventDamage);
			targetData.Amount += eventDamage.Amount;
			data = targetData.GetSpellData (eventDamage);
			data.TotalAmount += eventDamage.Amount;
			data.Max = Math.Max (data.Max, eventDamage.Amount);
			data.Min = Math.Min (data.Min, eventDamage.Amount);
			data.Times++;
			
			actor = visitor.allEvents.GetActor(eventDamage);
			tagData = actor.GetTagData (() => new DamageTag());
			tagData.TotalDamage += eventDamage.Amount;
			data = tagData.GetSpellData (eventDamage);
			data.TotalAmount += eventDamage.Amount;
			data.Max = Math.Max (data.Max, eventDamage.Amount);
			data.Min = Math.Min (data.Min, eventDamage.Amount);
			data.Times++;
			
			targetData = tagData.GetTargetData (eventDamage);
			targetData.Amount += eventDamage.Amount;
			data = targetData.GetSpellData (eventDamage);
			data.TotalAmount += eventDamage.Amount;
			data.Max = Math.Max (data.Max, eventDamage.Amount);
			data.Min = Math.Min (data.Min, eventDamage.Amount);
			data.Times++;
		}
		#endregion

		public override int Compare (Actor left, Actor right)
		{
			return left.GetTagData (() => new DamageTag()).TotalDamage.CompareTo (right.GetTagData (() => new DamageTag()).TotalDamage);
		}
		
		public override string GetHintText(EventInformation eventInformation, Actor actor)
		{
			var battleSeconds = eventInformation.TotalSeconds == 0 ? (actor.LastAction - actor.FirstAction).TotalSeconds : eventInformation.TotalSeconds;
			var tagData = actor.GetTagData (() => new DamageTag());
			var totalDamage = tagData.TotalDamage;
			var dps = battleSeconds > 0 ? (long)(totalDamage / battleSeconds) : 0;
			return totalDamage +  "(" + dps  + " dps)";
		}
		
		class TotalData
		{
			public string Name { get; set; }
			public string TargetName { get; set; }
			public long TotalDamage { get; set; }
			public long Damage { get; set; }
			public long CritDamage { get; set; }
			public long Min = long.MaxValue, Max = long.MinValue, Count = 0;
			public long CritMin = long.MaxValue, CritMax = long.MinValue, CritCount = 0;
			public long Avoided, Parried, Missed, Resisted;
			public long Percent { get; set; }
		}
		
		class AbilityContainer {
			public TotalData TotalData { get; set; }
			public string Name { get; set; }
			public long Damage { get; set; }
			public long Percent { get; set; }
		}
		
		class AbilityDetailContainer {
			public string Type  { get; set; }
			public long Min { get; set; }
			public long Avg { get; set; }
			public long Max { get; set; }
			public long Count { get; set; }
			public long Percent { get; set; }
			
			public AbilityDetailContainer(string type, long min, long avg, long max, long count, long percent)
			{
				this.Type = type;
				this.Min = min;
				this.Avg = avg;
				this.Max = max;
				this.Count = count;
				this.Percent = percent;
			}
		}
		
		public override void CreateDetailsView(ListView abilities, ListView details, Actor actor)
		{
			// collect data
			var tagData = actor.GetTagData (() => new DamageTag());
			var normal = tagData.Damage;
			var crit = tagData.CritDamage;
			Dictionary<string, TotalData> collectedData = new Dictionary<string, TotalData> ();
			long totalDamage = 0;
			
			foreach (SpellData data in normal.Values) {
				totalDamage += data.TotalAmount;
				TotalData total = new TotalData () { 
					Damage = data.TotalAmount,
					Name = data.SpellName,
					Min = data.Min, 
					Max = data.Max,
					Count = data.Times,
					Avoided = data.Avoided,
					Parried = data.Parried,
					Missed = data.Missed,
					Resisted = data.Resisted};
				collectedData[data.SpellId] = total;
			}
			
			foreach (SpellData data in crit.Values) {
				totalDamage += data.TotalAmount;
				TotalData total;
				if (!collectedData.TryGetValue (data.SpellId, out total))
					collectedData[data.SpellId] = total = new TotalData () { Name = data.SpellName,
					Avoided = data.Avoided,
					Parried = data.Parried,
					Missed = data.Missed,
					Resisted = data.Resisted
				};
				total.CritDamage = data.TotalAmount;
				total.CritMin = data.Min;
				total.CritMax = data.Max;
				total.CritCount = data.Times;
			}
			// create views
			var abilityGrid = new GridView ();
			abilities.View = abilityGrid;
			abilityGrid.Columns.Add (new GridViewColumn () { Header = "Ability Name", DisplayMemberBinding = new Binding("Name")});
			abilityGrid.Columns.Add (new GridViewColumn () { Header = "Damage", DisplayMemberBinding = new Binding("Damage")});
			abilityGrid.Columns.Add (new GridViewColumn () { Header = "%", DisplayMemberBinding = new Binding("Percent")});
			
			abilities.ItemsSource = collectedData.Values.Select (totalData => {
			                                                     	long allDmg = totalData.Damage + totalData.CritDamage;
			                                                     	totalData.Percent = allDmg * 100 / totalDamage;
			                                                     	return new AbilityContainer () {
			                                                     		TotalData = totalData,
			                                                     		Name = totalData.Name,
			                                                     		Damage = allDmg,
			                                                     		Percent = totalData.Percent
			                                                     	};
			});
			
			var detailsGrid = new GridView ();
			details.View = detailsGrid;
			detailsGrid.Columns.Add (new GridViewColumn () { Header = "Type", DisplayMemberBinding = new Binding("Type")});
			detailsGrid.Columns.Add (new GridViewColumn () { Header = "Min", DisplayMemberBinding = new Binding("Min")});
			detailsGrid.Columns.Add (new GridViewColumn () { Header = "Avg", DisplayMemberBinding = new Binding("Avg")});
			detailsGrid.Columns.Add (new GridViewColumn () { Header = "Max", DisplayMemberBinding = new Binding("Max")});
			detailsGrid.Columns.Add (new GridViewColumn () { Header = "Count", DisplayMemberBinding = new Binding("Count")});
			detailsGrid.Columns.Add (new GridViewColumn () { Header = "%", DisplayMemberBinding = new Binding("Percent")});
			
			
			abilities.SelectionChanged += delegate(object sender, SelectionChangedEventArgs e) { 
				var d = (AbilityContainer)e.AddedItems[0];
				if (d == null)
					return;
				var list = new List<AbilityDetailContainer> ();
				
				var data = d.TotalData;
				long totalCount = data.Count + data.CritCount + data.Avoided + data.Parried + data.Missed + data.Resisted;
				if (data.Count > 0)
					list.Add (new AbilityDetailContainer ("Hit", data.Min, data.Damage / data.Count, data.Max, data.Count, data.Count * 100 / totalCount));
				if (data.CritCount > 0)
					list.Add (new AbilityDetailContainer ("Crit", data.CritMin, data.CritDamage / data.CritCount, data.CritMax, data.CritCount, data.CritCount * 100 / totalCount));
				if (data.Avoided > 0)
					list.Add (new AbilityDetailContainer ("Avoid", 0, 0, 0, data.Avoided, data.Avoided * 100 / totalCount));
				if (data.Parried > 0)
					list.Add (new AbilityDetailContainer ("Parry", 0, 0, 0, data.Parried, data.Parried * 100 / totalCount));
				if (data.Resisted > 0)
					list.Add (new AbilityDetailContainer ("Resist", 0, 0, 0, data.Resisted, data.Resisted * 100 / totalCount));
				if (data.Missed > 0)
					list.Add (new AbilityDetailContainer ("Miss", 0, 0, 0, data.Missed, data.Missed * 100 / totalCount));
				
				details.ItemsSource = list;
			};
		}
		
	}
}
